package sk.stuba.fiit.clientApp.service.bussiness;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Random;
import java.util.TimeZone;

import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.etsi.uri._01903.v1_3.AnyType;
import org.etsi.uri._01903.v1_3.CertIDListType;
import org.etsi.uri._01903.v1_3.CertIDType;
import org.etsi.uri._01903.v1_3.CertificateValuesType;
import org.etsi.uri._01903.v1_3.DigestAlgAndValueType;
import org.etsi.uri._01903.v1_3.EncapsulatedPKIDataType;
import org.etsi.uri._01903.v1_3.SignedDataObjectPropertiesType;
import org.etsi.uri._01903.v1_3.SignedPropertiesType;
import org.etsi.uri._01903.v1_3.SignedSignaturePropertiesType;
import org.etsi.uri._01903.v1_3.UnsignedDataObjectPropertiesType;
import org.etsi.uri._01903.v1_3.UnsignedPropertiesType;
import org.etsi.uri._01903.v1_3.UnsignedSignaturePropertiesType;
import org.etsi.uri._01903.v1_3.XAdESTimeStampType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.w3._2000._09.xmldsig.CanonicalizationMethodType;
import org.w3._2000._09.xmldsig.DigestMethodType;
import org.w3._2000._09.xmldsig.KeyInfoType;
import org.w3._2000._09.xmldsig.ReferenceType;
import org.w3._2000._09.xmldsig.SignatureMethodType;
import org.w3._2000._09.xmldsig.SignatureType;
import org.w3._2000._09.xmldsig.SignatureValueType;
import org.w3._2000._09.xmldsig.SignedInfoType;
import org.w3._2000._09.xmldsig.TransformType;
import org.w3._2000._09.xmldsig.TransformsType;
import org.w3._2000._09.xmldsig.X509DataType;
import org.w3._2000._09.xmldsig.X509IssuerSerialType;
import org.xml.sax.SAXException;

import com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl;
import com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException;
import com.sun.org.apache.xml.internal.security.c14n.Canonicalizer;
import com.sun.org.apache.xml.internal.security.c14n.InvalidCanonicalizerException;


import sk.stuba.fiit.clientApp.service.CryptographyService;
import sk.stuba.fiit.clientApp.service.impl.CryptographyServiceImpl;
import sk.stuba.fiit.clientApp.singeltons.ApplicationContextSingelton;
import sk.stuba.fiit.clientApp.util.Util;
import sk.stuba.fiit.ltasys.ws.schema.beans.InitialTimeStamp;

public class XMLSignerCreator {
	private static XMLSignerCreator factory;

	@Autowired
	private CryptographyService cryptographyService = new CryptographyServiceImpl();

	public String generateUnicateId() {
		
		return "Id" + System.currentTimeMillis();
	}

	public SignedPropertiesType generateSignedProperties(byte[] certDigest,
			X509Certificate certificate, List<byte[]> timestamp, String digestMethod) {
		
		SignedSignaturePropertiesType sspt = new SignedSignaturePropertiesType();
		GregorianCalendar cal = new GregorianCalendar(TimeZone.getDefault());
		XMLGregorianCalendar calendar = new XMLGregorianCalendarImpl(cal);
		sspt.setSigningTime(calendar);

		CertIDListType certIdList = new CertIDListType();
		CertIDType certId = new CertIDType();
		DigestAlgAndValueType digestAlgAndValue = new DigestAlgAndValueType();
		DigestMethodType digestMethodType = new DigestMethodType();
		digestMethodType.setAlgorithm(Util.XMLDSIG_PREFIX + digestMethod);

		digestAlgAndValue.setDigestValue(certDigest);
		digestAlgAndValue.setDigestMethod(digestMethodType);
		certId.setCertDigest(digestAlgAndValue);
		X509IssuerSerialType issuerSerial = new X509IssuerSerialType();
		issuerSerial.setX509IssuerName(certificate.getIssuerDN().toString());
		issuerSerial.setX509SerialNumber(certificate.getSerialNumber());
		certId.setIssuerSerial(issuerSerial);
		certIdList.withCert(certId);
		sspt.setSigningCertificate(certIdList);

		SignedPropertiesType spt = new SignedPropertiesType();
		spt.setId(generateUnicateId());
		spt.setSignedSignatureProperties(sspt);

		SignedDataObjectPropertiesType sdopt = new SignedDataObjectPropertiesType();

		CanonicalizationMethodType cmt = new CanonicalizationMethodType();
		cmt.setAlgorithm(Util.CANONICALIZATION_OMITS_COMMENTS1_1);
		for(int i = 0; i< timestamp.size(); i++){
			XAdESTimeStampType timstampType = new XAdESTimeStampType();
			EncapsulatedPKIDataType epkidt = new EncapsulatedPKIDataType();
			epkidt.setValue(timestamp.get(i));
			timstampType.withCanonicalizationMethod(cmt);
			timstampType.withEncapsulatedTimeStampOrXMLTimeStamp(epkidt);
			sdopt.withAllDataObjectsTimeStamp(timstampType);
		}
		
		spt.setSignedDataObjectProperties(sdopt);
		return spt;
	}

	public UnsignedPropertiesType generateUnsignedProperties(byte[] timestamp) {

		UnsignedPropertiesType upt = new UnsignedPropertiesType();
		UnsignedSignaturePropertiesType uspt = new UnsignedSignaturePropertiesType();
		XAdESTimeStampType timstampType = new XAdESTimeStampType();
		CanonicalizationMethodType cmt = new CanonicalizationMethodType();
		cmt.setAlgorithm(Util.CANONICALIZATION_OMITS_COMMENTS1_1);
		EncapsulatedPKIDataType epkidt = new EncapsulatedPKIDataType();
		epkidt.setValue(timestamp);
		timstampType.withCanonicalizationMethod(cmt);
		timstampType.withEncapsulatedTimeStampOrXMLTimeStamp(epkidt);
		uspt.withCounterSignatureOrSignatureTimeStampOrCompleteCertificateRefs(timstampType);

		upt.setUnsignedSignatureProperties(uspt);
		return upt;
	}

	public SignedInfoType setSignedInfo(List<ReferenceBussinessData> reference,
			String canonicalizationMethod, String sigAlgorithm) {

		SignedInfoType result = new SignedInfoType();
		result.setId(generateUnicateId());
		CanonicalizationMethodType canMethod = new CanonicalizationMethodType();
		canMethod.setAlgorithm(canonicalizationMethod);
		result.setCanonicalizationMethod(canMethod);
		SignatureMethodType sigMethod = new SignatureMethodType();
		sigMethod.setAlgorithm(sigAlgorithm);
		result.setSignatureMethod(sigMethod);

		List<ReferenceType> xmlRef = new ArrayList<ReferenceType>();
		TransformsType transform = new TransformsType();
		TransformType transType = new TransformType();
		transType.setAlgorithm(canonicalizationMethod);
		transform.withTransform(transType);

		for (ReferenceBussinessData referenceCVO : reference) {
			ReferenceType ref = new ReferenceType();
			ref.setId(generateUnicateId());
			DigestMethodType dmt = new DigestMethodType();
			dmt.setAlgorithm(referenceCVO.getDigestMethod());
			ref.setDigestMethod(dmt);
			ref.setDigestValue(referenceCVO.getDigestValue());
			ref.setURI(referenceCVO.getUrn());
			ref.withTransforms(transform);
			xmlRef.add(ref);
		}

		result.withReference(xmlRef);
		return result;
	}

	public static ApplicationContext singelton = ApplicationContextSingelton.getInstance();
	private final static String  JAXB_BEAN = "marshaller-client";
	public static Jaxb2Marshaller controler =  (Jaxb2Marshaller) singelton.getBean(JAXB_BEAN);
	
	public static SignedInfoType canonicalize(SignedInfoType signatureType) throws InvalidCanonicalizerException, CanonicalizationException, ParserConfigurationException, IOException, SAXException{	
		StringWriter writer = new StringWriter();
		Result result = new StreamResult(writer);
		controler.marshal(signatureType, result);
		byte [] data = result.toString().getBytes();
		com.sun.org.apache.xml.internal.security.Init.init();
		Canonicalizer canon = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS);
		byte canonXmlBytes[] = canon.canonicalize(data);
		String canonXmlString = new String(canonXmlBytes);
		
		ByteArrayInputStream bais = new ByteArrayInputStream(canonXmlString.getBytes());
		Source source = new StreamSource(bais);
		SignedInfoType stresult = (SignedInfoType) controler.unmarshal(source);
		return stresult;
	}
	
	public SignatureValueType generateSignatureValue(String signAlgorithm,
			PrivateKey privateKey, SignatureType signatureType)
			throws InvalidKeyException, NoSuchAlgorithmException,
			NoSuchProviderException, SignatureException {
		
		SignedInfoType canonicalizeSignatureType;
		try{
			canonicalizeSignatureType= canonicalize(signatureType.getSignedInfo());
		}catch (Exception e) {
			canonicalizeSignatureType = signatureType.getSignedInfo();
		}
		SignedInfoType signedInfoType = canonicalizeSignatureType;
		List<ReferenceType> reference = signedInfoType.getReference();
		List<Object> digest = new ArrayList<Object>();
		for (ReferenceType referenceType : reference) {
			digest.add(referenceType.getDigestValue());
		}

		SignatureValueType resul = new SignatureValueType();
		resul.setValue(cryptographyService.calculateSignatureFromHashData(
				signAlgorithm, privateKey, digest));
		resul.setId(generateUnicateId());
		return resul;
	}

	public KeyInfoType generateKeyInfoType(X509Certificate certificate)
			throws CertificateEncodingException {

		KeyInfoType result = new KeyInfoType();
		X509DataType x509DataType = new X509DataType();

		EncapsulatedPKIDataType encCert = new EncapsulatedPKIDataType();
		encCert.setValue(certificate.getEncoded());

		CertificateValuesType cvt = new CertificateValuesType();
		cvt.withEncapsulatedX509CertificateOrOtherCertificate(encCert);

		x509DataType.withX509IssuerSerialOrX509SKIOrX509SubjectName(cvt);

		result.withContent(x509DataType);
		return result;
	}

	public static XMLSignerCreator getInstance() {
		if (factory == null)
			return new XMLSignerCreator();
		return factory;

	}

}
